package apig.algorithm.fingerprints;

import java.io.*;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;

import apii.algorithm.*;
import apii.algorithm.tokenizer.*;
import apii.algorithm.tokenizer.Tokenizer.Token;
import apii.server.ServerThings;

/**
 * <b>Note:</b> thread SAFE.
 * <p>
 * <b> Note:</b> Doesn't create threads.
 * 
 * @author Alex
 */
public class FingerprintsAlgorithm implements Algorithm {

	private static final int K = 20;
	private static final int T = 40;
	private static final int W = T - K + 1;
	private static final long HASH_POW = 89528293;
	private static final long HASH_MAX;
	static {
		long p = 1;
		for (int i = 0; i < K - 1; ++i)
			p *= HASH_POW;
		HASH_MAX = p;
	}

	static final Object PRESENTATION_LOCK = new Object();

	@Override
	public void inspect(Connection c, int codeId, Iterator<Integer> codeIds,
			ProgressMonitor pm) throws SQLException {
		preparePresentations(codeId);
		try (Statement st = c.createStatement();
				Statement st1 = c.createStatement();) {
			ResultSet rs;
			rs = st.executeQuery("select f2.code_id, f1.start, f1.end, f2.start, f2.end, length(c1.code), length(c2.code) from fingerprints f1 join fingerprints f2 on f1.hash = f2.hash join codes c2 on f2.code_id = c2.code_id, codes c1 where f1.code_id = "
					+ codeId
					+ " and f1.code_id > f2.code_id and c1.user_id != c2.user_id and c1.code_id = "
					+ codeId + " order by 1");

			int codeId1 = -1;
			int length1 = -1;
			int length2 = -1;
			List<HypoImpl> list = new ArrayList<HypoImpl>();
			for (;;) {
				boolean haveNext = rs.next();
				if (!haveNext || rs.getInt(1) != codeId1) {
					compress(list);
					double veracity = getVeracity(list, length1, length2);
					for (HypoImpl hypo : list)
						st1.execute("insert into hypos values(next value for hypos,"
								+ codeId
								+ ","
								+ codeId1
								+ ","
								+ veracity
								+ ","
								+ asCsv(hypo) + ")");
					list.clear();
					if (!haveNext)
						break;
				}
				codeId1 = rs.getInt(1);
				length1 = rs.getInt(6);
				length2 = rs.getInt(7);
				list.add(new HypoImpl(rs.getInt(2), rs.getInt(3), rs.getInt(4),
						rs.getInt(5)));
			}
		}
	}

	void preparePresentations(int codeId) throws SQLException {
		synchronized (PRESENTATION_LOCK) {
			try (Connection c = ServerThings.getThings().getConnection();
					Statement st = c.createStatement();
					Statement st1 = c.createStatement();) {
				ResultSet rs;
				st.execute("create table if not exists fingerprints (hash bigint not null, code_id int not null, start int not null, end int not null)");
				st.execute("create index if not exists hash on fingerprints(hash)");
				st.execute("create index if not exists code_id on fingerprints(code_id)");
				rs = st.executeQuery("select code_id, code from codes where code_id between (select ifnull(max(code_id), -1) from fingerprints) + 1 and "
						+ codeId);
				JavaTokenizer tok = new JavaTokenizer();
				while (rs.next()) {
					MinQueue<Long> q = new MinQueue<Long>(W);
					int id = rs.getInt(1);
					Token[] tokens;
					try {
						tokens = tok.parse(rs.getString(2));
					} catch (MalformedCodeException e) {
						e.printStackTrace();
						continue;
					}
					long hash = 0;
					int prevPos = -1;
					for (int i = 0; i < tokens.length; ++i) {
						hash = hash * HASH_POW + tokens[i].getID();
						if (K - 1 <= i) {
							q.push(hash);
							if (T - 1 <= i) {
								int pos = q.getMinPos() + i + 1 - T;
								if (pos != prevPos) {
									prevPos = pos;
									st1.execute("insert into fingerprints values("
											+ q.getMin()
											+ ","
											+ id
											+ ","
											+ tokens[pos].getStart()
											+ ","
											+ tokens[pos + K - 1].getEnd()
											+ ")");
								}
								q.pop();
							}
							hash -= HASH_MAX * tokens[i + 1 - K].getID();
						}
					}
				}
				rs.close();
				c.commit();
			}
		}
	}

	private static double getVeracity(List<? extends Hypo> list, int length1,
			int length2) {
		int sum1 = 0;
		int sum2 = 0;
		for (Hypo h : list) {
			sum1 += h.getEnd1() - h.getStart1();
			sum2 += h.getEnd2() - h.getStart2();
		}
		return Math.min(Math.max(sum1 * 1. / length1, sum2 * 1. / length2), 1);
	}

	private static class HypoInfo {
		Hypo h;
		int[] ind;

		public HypoInfo(Hypo h) {
			this.h = h;
		}

		public int getIndex(int n) {
			return n == 0 ? h.getStart1() : h.getStart2();
		}
	}

	public static void compress(final List<? extends Hypo> list) {
		// List<HypoInfo> hypos = new ArrayList<HypoInfo>(list.size());
		// for (int i = 0; i < list.size(); ++i)
		// hypos.add(new HypoInfo(list.get(i)));
		// for (;;) {
		// for (int num = 0; num < 2; ++num) {
		// final int num1 = num;
		// Collections.sort(hypos, new Comparator<HypoInfo>() {
		//
		// @Override
		// public int compare(HypoInfo h1, HypoInfo h2) {
		// return ((Integer) h1.getIndex(num1)).compareTo(h2
		// .getIndex(num1));
		// }
		// });
		// int ind = 0;
		// for (int i = 0; i < list.size(); ++ind) {
		// }
		// }
		// }
	}

	public static String asCsv(Hypo h) {
		return h.getStart1() + "," + h.getEnd1() + "," + h.getStart2() + ","
				+ h.getEnd2();
	}

	public static void main(String[] args) throws SQLException {
		try (Connection c = ServerThings.getThings().getConnection();) {
			FingerprintsAlgorithm algo = new FingerprintsAlgorithm();
			algo.inspect(c, 7, null, null);
			algo.inspect(c, 8, null, null);
			algo.inspect(c, 6, null, null);
			c.commit();
		}
	}
}
